# -*- coding: utf-8 -*-
from google.appengine.ext import db
import os, sys

def patch_all():
    patch_python()
    patch_app_engine()
    patch_django()
    setup_logging()

def patch_python():
    # Remove modules that we want to override
    for module in ('httplib', 'urllib', 'urllib2', 'memcache',):
        if module in sys.modules:
            del sys.modules[module]

    # For some reason the imp module can't be replaced via sys.path
    from appenginepatcher import have_appserver
    if have_appserver:
        from appenginepatcher import imp
        sys.modules['imp'] = imp

    # Add fake error and gaierror to socket module. Required for boto support.
    import socket
    class error(Exception):
        pass
    class gaierror(Exception):
        pass
    socket.error = error
    socket.gaierror = gaierror

    if have_appserver:
        def unlink(_):
            raise NotImplementedError('App Engine does not support FS writes!')
        os.unlink = unlink

def patch_app_engine():
    # This allows for using Paginator on a Query object. We limit the number
    # of results to 301, so there won't be any timeouts (301 because you can
    # say "more than 300 results").
    def __len__(self):
        return self.count(301)
    db.Query.__len__ = __len__

    # Add "model" property to Query (needed by generic views)
    class ModelProperty(object):
        def __get__(self, query, unused):
            return query._model_class
    db.Query.model = ModelProperty()

    # Add _meta to Model, so porting code becomes easier (generic views and
    # xheaders depend on it).
    class _meta(object):
        def __init__(self, model):
            self.app_label = model.__module__.split('.')[-2]
            self.object_name = model.__name__
            self.verbose_name = self.object_name
    class Meta(object):
        def __get__(self, unused, model):
            # Cache _meta
            model._meta = _meta(model)
            return model._meta
    db.Model._meta = Meta()

    # Replace save() method with one that calls put(), so a monkey-patched
    # put() will also work if someone uses save()
    def save(self):
        return self.put()
    db.Model.save = save

def patch_django():
    # In order speed things up and consume less memory we lazily replace
    # modules if possible. This requires some __path__ magic. :)

    # Add fake 'appengine' DB backend
    # This also creates a separate datastore for each project.
    from appenginepatcher.db_backends import appengine
    sys.modules['django.db.backends.appengine'] = appengine

    base_path = os.path.abspath(os.path.dirname(__file__))

    # Replace generic views
    from django.views import generic
    generic.__path__.insert(0, os.path.join(base_path, 'generic_views'))

    # Replace db session backend and tests
    from django.contrib import sessions
    sessions.__path__.insert(0, os.path.join(base_path, 'sessions'))
    from django.contrib.sessions import backends
    backends.__path__.insert(0, os.path.join(base_path, 'session_backends'))

    # Replace the dispatchers.
    import logging
    from django.core import signals

    def log_exception(*args, **kwargs):
        logging.exception('Exception in request:')

    # Log errors.
    signals.got_request_exception.connect(log_exception)

    # Unregister the rollback event handler.
    import django.db
    signals.got_request_exception.disconnect(django.db._rollback_on_exception)

    # Replace auth models
    # This MUST happen before any other modules import User or they'll
    # get Django's original User model!!!
    from appenginepatcher.auth import models
    sys.modules['django.contrib.auth.models'] = models

    # Replace rest of auth app
    from django.contrib import auth
    auth.__path__.insert(0, os.path.join(base_path, 'auth'))

    # Replace ModelForm
    # This MUST happen as early as possible, but after User got replaced!
    from google.appengine.ext.db import djangoforms as aeforms
    from django import forms
    from django.forms import models as modelforms
    forms.ModelForm = modelforms.ModelForm = aeforms.ModelForm
    forms.ModelFormMetaclass = aeforms.ModelFormMetaclass
    modelforms.ModelFormMetaclass = aeforms.ModelFormMetaclass

    # Fix handling of verbose_name. Google resolves lazy translation objects
    # immedately which of course breaks translation support.
    from django.utils.text import capfirst
    def get_form_field(self, form_class=forms.CharField, **kwargs):
        defaults = {'required': self.required}
        if self.verbose_name:
            defaults['label'] = capfirst(self.verbose_name)
        if self.choices:
            choices = []
            if not self.required or (self.default is None and
                                     'initial' not in kwargs):
                choices.append(('', '---------'))
            for choice in self.choices:
                choices.append((str(choice), unicode(choice)))
            defaults['widget'] = forms.Select(choices=choices)
        if self.default is not None:
            defaults['initial'] = self.default
        defaults.update(kwargs)
        return form_class(**defaults)
    db.Property.get_form_field = get_form_field

    # Extend ModelForm with support for EmailProperty
    def get_form_field(self, **kwargs):
        """Return a Django form field appropriate for an email property."""
        defaults = {'form_class': forms.EmailField}
        defaults.update(kwargs)
        return super(db.EmailProperty, self).get_form_field(**defaults)
    db.EmailProperty.get_form_field = get_form_field

    # Replace mail backend
    from appenginepatcher import mail as gmail
    from django.core import mail
    mail.SMTPConnection = gmail.GoogleSMTPConnection
    mail.mail_admins = gmail.mail_admins
    mail.mail_managers = gmail.mail_managers

    # Activate ragendja's GLOBALTAGS support (automatically done on import)
    from ragendja import template

    # Patch auth forms
    from appenginepatcher import auth_forms_patch

def setup_logging():
    from django.conf import settings
    import logging
    if settings.DEBUG:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)
